/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * Key Variant Factory Class
 *      Provides a static class for generating derived key sets.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.Security.Cryptography;

namespace Adaptive.Foundation.Security
{
    /// <summary>
    /// Provides a static class for generating derived key sets.
    /// </summary>
    public static class KeyVariantFactory
    {
        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Creates a new derived key set for use in AES cryptographic operations.
        /// </summary>
        /// <param name="aesKey">The original key to derive from.</param>
        /// <param name="aesIv">The original IV to derive from.</param>
        /// <param name="sequenceCount">The number of new items to create.</param>
        /// <returns>
        /// A <see cref="DerivedKeySetCollection"/> instance containing the list of key variants.
        /// </returns>
        public static DerivedKeySetCollection CreateAesDerivedKeySet(byte[] aesKey, byte[] aesIv, int sequenceCount)
        {
            DerivedKeySetCollection returnValue = null;             //Return value.
            DerivedKeySet newItem = null;                           //New instance.
            int count = 0;                                          //Iteration counter.
            byte[] lastKey = null;                                  //Last key data.
            byte[] lastIV = null;                                   //Last IV data.

            //Initialize values for use.
            returnValue = new DerivedKeySetCollection();
            lastKey = aesKey;
            lastIV = aesIv;

            //Create the key variants and store.
            for (count = 0; count < sequenceCount; count++)
            {
                //Create the variant values.
                lastKey = CreateNewKeyDerivative(lastKey);
                lastIV = CreateNewIVDerivative(lastIV);

                //Store the new item.
                newItem = new DerivedKeySet();
                newItem.Index = count;
                newItem.Key = lastKey;
                newItem.IV = lastIV;
                returnValue.Add(newItem);

                if (count % 5 == 0)
                    System.Windows.Forms.Application.DoEvents();
            }

            lastKey = null;
            lastIV = null;
            newItem = null;

            return returnValue;
        }
        /// <summary>
        /// Creates a new encryption/decryption key derivative from the supplied key and seed values.
        /// </summary>
        /// <param name="originalData">
        /// The key data to derive from.  This value must be 32 bytes.
        /// </param>
        /// <returns>
        /// A new derivative of the original value.
        /// </returns>
        public static byte[] CreateNewKeyDerivative(byte[] originalData)
        {
            byte[] returnValue = null;                      //Return value.
            byte[] leftValue = null;                        //Left side of the array.
            byte[] rightValue = null;                       //Right side of the array.

            //Split the original value into two arrays for processing.
            leftValue = new byte[16];
            rightValue = new byte[16];
            Array.Copy(originalData, 0, leftValue, 0, 16);
            Array.Copy(originalData, 16, rightValue, 0, 16);

            //Transform the key part values.
            leftValue = DeriveKeyPart(leftValue);
            rightValue = DeriveKeyPart(rightValue);

            //Re-combine the key values.
            returnValue = new byte[32];
            Array.Copy(rightValue, 0, returnValue, 0, 16);
            Array.Copy(leftValue, 0, returnValue, 16, 16);

            //Clear and return.
            Array.Clear(leftValue, 0, 16);
            Array.Clear(rightValue, 0, 16);
            rightValue = null;
            leftValue = null;
            return returnValue;
        }
        /// <summary>
        /// Creates a new encryption/decryption IV derivative from the supplied IV and seed values.
        /// </summary>
        /// <param name="originalData">
        /// The IV data to derive from.  This value must be 16 bytes.
        /// </param>
        /// <returns>
        /// A new derivative of the original value.
        /// </returns>        
        public static byte[] CreateNewIVDerivative(byte[] originalData)
        {
            byte[] returnValue = null;                  //Return value.

            //Create the deviation.
            returnValue = DeriveKeyPart(DeviateKeyPart(originalData));

            return returnValue;

        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Converts the supplied byte array into two long integer values.
        /// </summary>
        /// <param name="original">
        /// A 32-element byte array.
        /// </param>
        /// <returns>
        /// A 2-element array of long integers.
        /// </returns>
        private static long[] ToNumber(byte[] original)
        {
            long[] returnValue = new long[2];
            byte[] left = new byte[8];
            byte[] right = new byte[8];

            Array.Copy(original, 0, left, 0, 8);
            Array.Copy(original, 8, right, 0, 8);

            returnValue[0] = Conversion.ToInt64(left);
            returnValue[1] = Conversion.ToInt64(right);
            return returnValue;
        }
        /// <summary>
        /// Converts the supplied two long integers into a single byte array.
        /// </summary>
        /// <param name="left">
        /// The integer containing the most significant bits.
        /// </param>
        /// <param name="right">
        /// The integer containing the least significant bits.
        /// </param>
        /// <returns>
        /// A byte array for the values.
        /// </returns>
        private static byte[] ToBytes(long left, long right)
        {
            byte[] dataL = Conversion.ToBytes(left);
            byte[] dataR = Conversion.ToBytes(right);
            byte[] returnValue = new byte[16];

            Array.Copy(dataL, 0, returnValue, 0, 8);
            Array.Copy(dataR, 0, returnValue, 8, 8);
            return returnValue;
        }
        /// <summary>
        /// Creates a new key part derivative by XORing the byte values.
        /// </summary>
        /// <param name="original">
        /// The original data content to be modified.
        /// </param>
        /// <returns>
        /// The new key part derivative.
        /// </returns>
        private static byte[] DeriveKeyPart(byte[] original)
        {
            Rfc2898DeriveBytes deriver = null;
            byte[] returnValue = null;
            byte[] salt = new byte[8];

            Array.Copy(original, 0, salt, 0, 8);
            deriver = new Rfc2898DeriveBytes(original, salt, 1000);
            returnValue = deriver.GetBytes(original.Length);
            return returnValue;
        }
        /// <summary>
        /// Performs a matrix transformation of the supplied data.
        /// </summary>
        /// <param name="original">
        /// A sixteen (16) element byte array to be transformed in a 4 x 4 matrix.
        /// </param>
        /// <returns>
        /// The derived byte array.
        /// </returns>
        private static byte[] DeviateKeyPart(byte[] original)
        {
            Rfc2898DeriveBytes deriver = null;
            byte[] returnValue = null;
            byte[] salt = new byte[8];

            Array.Copy(original, original.Length-8, salt, 0, 8);
            deriver = new Rfc2898DeriveBytes(original, salt, 1000);
            returnValue = deriver.GetBytes(original.Length);
            return returnValue;
        }
        /// <summary>
        /// Performs a geometric modification on the supplied salt value.
        /// </summary>
        /// <param name="sequence">The value to modify.</param>
        /// <returns>
        /// A new derived value.
        /// </returns>
        private static int ModifySequence(int sequence)
        {
            double div3 = Math.Pow(sequence, 3);
            double div2 = Math.Pow(sequence, 2);

            // x^2 - (x^3 / x^2)
            return (int)(div3 - (div3 / div2));
        }
        /// <summary>
        /// Swaps byte values in the supplied array to avoid pattern recurrances.
        /// </summary>
        /// <param name="original">
        /// The byte array to be shifted.
        /// </param>
        /// <returns>
        /// A new byte array with the first and second bytes swapped with the last and 
        /// next to last bytes, respectively.
        /// </returns>
        private static byte[] ObfuscationShift(byte[] original)
        {
            byte[] returnValue = new byte[original.Length];
            byte value = 0;

            Array.Copy(original, 0, returnValue, 0, returnValue.Length);
            value = original[0];
            returnValue[0] = original[original.Length - 1];
            returnValue[returnValue.Length - 1] = value;

            value = original[1];
            returnValue[1] = original[original.Length - 2];
            returnValue[returnValue.Length - 2] = value;

            return returnValue;
        }
        #endregion
    }
}
